package cn.iocoder.yudao.module.trade.order.service;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.framework.business.basic.pojo.vo.UserSimpleVO;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
import cn.iocoder.yudao.framework.qrcode.pojo.QrCodeVerifyType;
import cn.iocoder.yudao.framework.qrcode.util.QrCodeUtils;
import cn.iocoder.yudao.module.api.member.user.MemberUserApi;
import cn.iocoder.yudao.module.api.promotion.combination.CombinationRecordApi;
import cn.iocoder.yudao.module.api.promotion.combination.dto.CombinationRecordRespDTO;
import cn.iocoder.yudao.module.api.promotion.groupon.GrouponPackageApi;
import cn.iocoder.yudao.module.api.promotion.groupon.dto.GrouponPackageRespDTO;
import cn.iocoder.yudao.module.api.shop.info.ShopInfoApi;
import cn.iocoder.yudao.module.api.shop.info.dto.ShopInfoDTO;
import cn.iocoder.yudao.module.api.trade.delivery.ExpressApiFactory;
import cn.iocoder.yudao.module.api.trade.delivery.dto.ExpressTrackQueryReqDTO;
import cn.iocoder.yudao.module.api.trade.delivery.dto.ExpressTrackRespDTO;
import cn.iocoder.yudao.module.api.trade.order.dto.TradeOrderQueryDTO;
import cn.iocoder.yudao.module.api.trade.order.enums.TradeOrderRefundStatusEnum;
import cn.iocoder.yudao.module.api.trade.order.enums.TradeOrderStatusCodeEnum;
import cn.iocoder.yudao.module.api.trade.order.enums.TradeOrderStatusEnum;
import cn.iocoder.yudao.module.api.trade.order.enums.TradeOrderTypeEnum;
import cn.iocoder.yudao.module.api.trade.order.vo.AppTradeOrderPageVO;
import cn.iocoder.yudao.module.trade.aftersale.dal.mysql.AfterSaleMapper;
import cn.iocoder.yudao.module.trade.delivery.dal.dataobject.DeliveryExpressDO;
import cn.iocoder.yudao.module.trade.delivery.service.DeliveryExpressService;
import cn.iocoder.yudao.module.trade.order.convert.TradeOrderConvert;
import cn.iocoder.yudao.module.trade.order.dal.dataobject.TradeOrderDO;
import cn.iocoder.yudao.module.trade.order.dal.dataobject.TradeOrderItemDO;
import cn.iocoder.yudao.module.trade.order.dal.mysql.TradeOrderItemMapper;
import cn.iocoder.yudao.module.trade.order.dal.mysql.TradeOrderMapper;
import cn.iocoder.yudao.module.trade.order.vo.TradeOrderPageForVerifyReqVO;
import cn.iocoder.yudao.module.trade.order.vo.TradeOrderPageReqVO;
import cn.iocoder.yudao.module.trade.order.vo.TradeOrderStatusNameGetter;
import cn.iocoder.yudao.module.trade.order.vo.TradeOrderSummaryRespVO;
import jakarta.annotation.Nullable;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.*;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.module.api.trade.ErrorCodeConstants.EXPRESS_NOT_EXISTS;
import static cn.iocoder.yudao.module.api.trade.ErrorCodeConstants.ORDER_NOT_FOUND;

/**
 * 交易订单【读】 Service 实现类
 *
 * @author 芋道源码
 */
@Slf4j
@Service
public class TradeOrderQueryServiceImpl implements TradeOrderQueryService, TradeOrderStatusNameGetter {
    private static final String TRADE_EXPRESS_TRACK = "xianmin:trade:express_track";

    @Resource
    private ExpressApiFactory expressApiFactory;

    @Resource
    private TradeOrderMapper tradeOrderMapper;
    @Resource
    private TradeOrderItemMapper tradeOrderItemMapper;
    @Resource
    private AfterSaleMapper afterSaleMapper;

    @Resource
    @Lazy // 延迟加载，避免循环依赖
    private DeliveryExpressService deliveryExpressService;
    @Resource
    @Lazy // 延迟加载，避免循环依赖
    private MemberUserApi memberUserApi;
    @Resource
    @Lazy // 延迟加载，避免循环依赖
    private ShopInfoApi shopInfoApi;
    @Resource
    @Lazy // 延迟加载，避免循环依赖
    private CombinationRecordApi combinationRecordApi;
    @Resource
    @Lazy // 延迟加载，避免循环依赖
    private GrouponPackageApi grouponPackageApi;

    // =================== Order ===================

    @Override
    public TradeOrderDO getOrder(Long id) {
        return tradeOrderMapper.selectById(id);
    }

    @Override
    public TradeOrderDO getOrderByNo(String no) {
        return tradeOrderMapper.selectByNo(no);
    }

    @Override
    public TradeOrderDO getOrder(Long userId, Long id) {
        TradeOrderDO order = tradeOrderMapper.selectById(id);
        if (order != null && ObjectUtil.notEqual(order.getUserId(), userId)) {
            return null;
        }
        return order;
    }

    @Override
    public TradeOrderDO getOrderByNo(Long userId, String no) {
        TradeOrderDO order = tradeOrderMapper.selectByNo(no);
        if (order != null && ObjectUtil.notEqual(order.getUserId(), userId)) {
            return null;
        }
        return order;
    }

    @Override
    public TradeOrderDO getOrderByUserIdAndStatusAndCombination(Long userId, Long combinationActivityId, Integer status) {
        return tradeOrderMapper.selectByUserIdAndCombinationActivityIdAndStatus(userId, combinationActivityId, status);
    }

    @Override
    public List<TradeOrderDO> getOrderList(Collection<Long> ids) {
        if (CollUtil.isEmpty(ids)) {
            return new ArrayList<>();
        }
        return tradeOrderMapper.selectByIds(ids);
    }

    @Override
    public PageResult<TradeOrderDO> getOrderPage(TradeOrderPageReqVO reqVO) {
        // 根据用户查询条件构建用户编号列表
        Set<Long> userIds = buildQueryConditionUserIds(reqVO);
        if (userIds == null) { // 没查询到用户，说明肯定也没他的订单；userIds 若为空集合则说明不需要用户查询条件。
            return PageResult.empty();
        }
        // 分页查询
        return tradeOrderMapper.selectPage(reqVO, userIds);
    }

    @Override
    public PageResult<TradeOrderDO> getOrderPageForVerify(TradeOrderPageForVerifyReqVO reqVO) {
        // 根据用户查询条件构建用户编号列表
        Set<Long> userIds = buildQueryConditionUserIds(BeanUtils.toBean(reqVO, TradeOrderPageReqVO.class));
        if (userIds == null) { // 没查询到用户，说明肯定也没他的订单
            return PageResult.empty();
        }
        return tradeOrderMapper.selectPageForVerify(reqVO, userIds);
    }

    @Nullable
    private Set<Long> buildQueryConditionUserIds(TradeOrderPageReqVO reqVO) {
        // 获得 userId 相关的查询
        Set<Long> userIds = new HashSet<>();
        if (StrUtil.isNotEmpty(reqVO.getUserMobile())) {
            UserSimpleVO user = memberUserApi.getUserByMobile(reqVO.getUserMobile());
            if (user == null) { // 没查询到用户，说明肯定也没他的订单
                return null;
            }
            userIds.add(user.getId());
        }
        if (StrUtil.isNotEmpty(reqVO.getUserNickname())) {
            List<UserSimpleVO> users = memberUserApi.getUserListByNickname(reqVO.getUserNickname());
            if (CollUtil.isEmpty(users)) { // 没查询到用户，说明肯定也没他的订单
                return null;
            }
            userIds.addAll(convertSet(users, UserSimpleVO::getId));
        }
        return userIds;
    }

    @Override
    public TradeOrderSummaryRespVO getOrderSummary(TradeOrderPageReqVO reqVO) {
        // 根据用户查询条件构建用户编号列表
        Set<Long> userIds = buildQueryConditionUserIds(reqVO);
        if (userIds == null) { // 没查询到用户，说明肯定也没他的订单
            return new TradeOrderSummaryRespVO();
        }
        // 查询每个售后状态对应的数量、金额
        List<Map<String, Object>> list = tradeOrderMapper.selectOrderSummaryGroupByRefundStatus(reqVO, null);

        TradeOrderSummaryRespVO respVO = new TradeOrderSummaryRespVO();
        respVO.setAfterSaleCount(0L);
        respVO.setAfterSalePrice(0L);
        for (Map<String, Object> map : list) {
            Long count = MapUtil.getLong(map, "count", 0L);
            Long price = MapUtil.getLong(map, "price", 0L);
            // 未退款的计入订单，部分退款、全部退款计入售后
            if (TradeOrderRefundStatusEnum.NONE.getStatus().equals(MapUtil.getInt(map, "refundStatus"))) {
                respVO.setOrderCount(count);
                respVO.setOrderPayPrice(price);
            } else {
                respVO.setAfterSaleCount(respVO.getAfterSaleCount() + count);
                respVO.setAfterSalePrice(respVO.getAfterSalePrice() + price);
            }
        }
        return respVO;
    }

    @Override
    public PageResult<TradeOrderDO> getOrderPageByUserId(Long userId, PageParam pageParam, TradeOrderQueryDTO queryDTO) {
        if (queryDTO.getAfterSaleStatus() != null) {
            List<Long> orderIds = afterSaleMapper.selectOrderIdsByUserIdAndStatus(userId, queryDTO.getAfterSaleStatus());
            if (CollUtil.isEmpty(orderIds)) {
                // 根据售后状态查不出售后单，说明肯定也没他的订单
                return PageResult.empty();
            }
            queryDTO.setIds(orderIds);
        } else if (TradeOrderStatusCodeEnum.REFUND.getCode().equals(queryDTO.getOrderStatus())) {
            List<Long> orderIds = afterSaleMapper.selectOrderIdsByUserId(userId);
            if (CollUtil.isEmpty(orderIds)) {
                // 查不出售后单，说明肯定也没他的订单
                return PageResult.empty();
            }
            queryDTO.setIds(orderIds);
            queryDTO.setOrderStatus(null);
        }
        return tradeOrderMapper.selectPageByUserId(userId, pageParam, queryDTO);
    }

    @Override
    @DataPermission(enable = false)
    public PageResult<TradeOrderDO> getOrderPageByShopId(Long shopId, PageParam pageParam, TradeOrderQueryDTO queryDTO) {
        if (queryDTO.getAfterSaleStatus() != null) {
            List<Long> orderIds = afterSaleMapper.selectOrderIdsByShopIdAndStatus(shopId, queryDTO.getAfterSaleStatus());
            if (CollUtil.isEmpty(orderIds)) {
                // 根据售后状态查不出售后单，说明肯定也没他的订单
                return PageResult.empty();
            }
            queryDTO.setIds(orderIds);
        } else if (TradeOrderStatusCodeEnum.REFUND.getCode().equals(queryDTO.getOrderStatus())) {
            List<Long> orderIds = afterSaleMapper.selectOrderIdsByShopId(shopId);
            if (CollUtil.isEmpty(orderIds)) {
                // 查不出售后单，说明肯定也没他的订单
                return PageResult.empty();
            }
            queryDTO.setIds(orderIds);
            queryDTO.setOrderStatus(null);
        }
        return tradeOrderMapper.selectPageByShopId(shopId, pageParam, queryDTO);
    }

    @Override
    public PageResult<TradeOrderDO> getOrderPageByStaffId(Long shopId, Long staffId, PageParam pageParam, Integer dineStatus) {
        return tradeOrderMapper.selectPageByStaffId(shopId, staffId, pageParam, dineStatus);
    }

    @Override
    @DataPermission(enable = false)
    public Integer getOrderCountByShopId(Long shopId, Integer status, Boolean commentStatus) {
        Long aLong = tradeOrderMapper.selectCountByShopIdAndStatus(shopId, status, commentStatus);
        return aLong == null ? 0 : aLong.intValue();
    }

    @Override
    public Integer getOrderCountByUserId(Long userId, List<Integer> deliveryTypes, Integer status, Boolean commentStatus) {
        Long aLong = tradeOrderMapper.selectCountByUserIdAndStatus(userId, deliveryTypes, status, commentStatus);
        return aLong == null ? 0 : aLong.intValue();
    }

    @Override
    public Integer getOrderCountByStaffId(Long shopId, Long staffId, @Nullable Integer ...dineStatus) {
        Long aLong = tradeOrderMapper.selectCountByStaffIdAndStatus(shopId, staffId, dineStatus);
        return aLong == null ? 0 : aLong.intValue();
    }

    @Override
    public List<ExpressTrackRespDTO> getExpressTrackList(Long id, Long userId) {
        // 查询订单
        TradeOrderDO order = tradeOrderMapper.selectById(id);
        if (order == null) {
            throw exception(ORDER_NOT_FOUND);
        }
        // 查询物流
        return getExpressTrackList(order);
    }

    @Override
    public List<ExpressTrackRespDTO> getExpressTrackList(Long id) {
        // 查询订单
        TradeOrderDO order = tradeOrderMapper.selectById(id);
        if (order == null) {
            throw exception(ORDER_NOT_FOUND);
        }
        // 查询物流
        return getExpressTrackList(order);
    }

    @Override
    public int getActivityProductCount(Long userId, Long activityId, TradeOrderTypeEnum type) {
        // 获得订单列表
        List<TradeOrderDO> orders = tradeOrderMapper.selectListByUserIdAndActivityId(userId, activityId, type);
        orders.removeIf(order -> TradeOrderStatusEnum.isClosed(order.getStatus())); // 过滤掉【已取消】的订单
        if (CollUtil.isEmpty(orders)) {
            return 0;
        }
        // 获得订单项列表
        return tradeOrderItemMapper.selectProductSumByOrderId(convertSet(orders, TradeOrderDO::getId));
    }

    /**
     * 获得订单的物流轨迹
     *
     * @param order 订单
     * @return 物流轨迹
     */
    private List<ExpressTrackRespDTO> getExpressTrackList(TradeOrderDO order) {
        LocalDateTime time = order.getDeliveryTime();
        if (time == null) {
            time = order.getPayTime();
        }
        if (time == null) {
            time = order.getCreateTime();
        }
        if (order.getLogisticsId() == null || order.getLogisticsId() <= 0) {
            ExpressTrackRespDTO expressTrackRespDTO = new ExpressTrackRespDTO();
            expressTrackRespDTO.setContent("货物准备中");
            expressTrackRespDTO.setTime(time);
            return List.of(expressTrackRespDTO);
        }
        try {
            // 查询快递公司
            DeliveryExpressDO express = deliveryExpressService.getDeliveryExpress(order.getLogisticsId());
            if (express == null) {
                throw exception(EXPRESS_NOT_EXISTS);
            }
            // 查询物流轨迹
            return getSelf().getExpressTrackList(express.getCode(), order.getLogisticsNo(), order.getReceiverMobile());
        } catch (Exception e) {
            ExpressTrackRespDTO expressTrackRespDTO = new ExpressTrackRespDTO();
            expressTrackRespDTO.setContent(e.getMessage());
            expressTrackRespDTO.setTime(order.getDeliveryTime());
            return List.of(expressTrackRespDTO);
        }
    }

    /**
     * 查询物流轨迹
     * <p>
     * 缓存的目的：考虑及时性要求不高，但是每次调用需要钱
     *
     * @param code           快递公司编码
     * @param logisticsNo    发货快递单号
     * @param receiverMobile 收、寄件人的电话号码
     * @return 物流轨迹
     */
    @Cacheable(cacheNames = TRADE_EXPRESS_TRACK, key = "#code + '-' + #logisticsNo + '-' + #receiverMobile", condition = "#result != null")
    public List<ExpressTrackRespDTO> getExpressTrackList(String code, String logisticsNo, String receiverMobile) {
        ExpressTrackQueryReqDTO dto = new ExpressTrackQueryReqDTO();
        dto.setExpressCode(code);
        dto.setLogisticsNo(logisticsNo);
        dto.setPhone(receiverMobile);
        return expressApiFactory.getDefaultExpressApi().getExpressTrackList(dto);
    }

    @Override
    public TradeOrderItemDO getOrderItem(Long userId, Long itemId) {
        TradeOrderItemDO orderItem = tradeOrderItemMapper.selectById(itemId);
        if (orderItem != null
            && ObjectUtil.notEqual(orderItem.getUserId(), userId)) {
            return null;
        }
        return orderItem;
    }

    @Override
    public TradeOrderItemDO getOrderItem(Long id) {
        return tradeOrderItemMapper.selectById(id);
    }

    @Override
    public List<TradeOrderItemDO> getOrderItemListByOrderIds(Collection<Long> orderIds) {
        if (CollUtil.isEmpty(orderIds)) {
            return Collections.emptyList();
        }
        return tradeOrderItemMapper.selectListByOrderId(orderIds);
    }

    @Override
    public PageResult<AppTradeOrderPageVO> toPageResult(PageResult<TradeOrderDO> pageResult, boolean isMerchant) {
        // 查询订单项
        List<TradeOrderItemDO> orderItems = getOrderItemListByOrderIds(
                convertSet(pageResult.getList(), TradeOrderDO::getId));
        // 最终组合
        PageResult<AppTradeOrderPageVO> data = TradeOrderConvert.INSTANCE.convertPage02(pageResult, orderItems);
        Map<Long, ShopInfoDTO> shops;
        if (isMerchant) {
            shops = Map.of();
        } else {
            Set<Long> shopIds = convertSet(data.getList(), AppTradeOrderPageVO::getShopId);
            shops = shopInfoApi.getShopInfoMap(shopIds);
        }
        Set<Long> combinationRecordIds = convertSet(data.getList(), AppTradeOrderPageVO::getCombinationRecordId);
        Map<Long, CombinationRecordRespDTO> combinationRecords = combinationRecordApi.getCombinationRecordsByIds(combinationRecordIds);
        Set<Long> grouponPackageIds = convertSet(data.getList(), AppTradeOrderPageVO::getGrouponPackageId);
        Map<Long, GrouponPackageRespDTO> grouponPackageMap = grouponPackageApi.getMap(grouponPackageIds);
        for (AppTradeOrderPageVO vo : data.getList()) {
            vo.setShop(shops.getOrDefault(vo.getShopId(), null));
            vo.setStatusName(assignStatusName(vo.getDeliveryType(), vo.getStatus(), vo.getCommentStatus(), vo.getRefundStatus(), vo.getDineStatus()));
            CombinationRecordRespDTO combinationRecordRespDTO = combinationRecords.getOrDefault(vo.getCombinationRecordId(), null);
            vo.setCombinationStatus(combinationRecordRespDTO == null ? null : combinationRecordRespDTO.getStatus());
            if (vo.getGrouponPackageId() != null && vo.getGrouponPackageId() > 0) {
                vo.setGrouponPackage(grouponPackageMap.getOrDefault(vo.getGrouponPackageId(), null));
            }
            if (!isMerchant && StrUtil.isNotBlank(vo.getVerifyCode()) && Boolean.TRUE.equals(vo.getPayStatus())) {
                QrCodeVerifyType type = new QrCodeVerifyType();
                type.setVerifyCode(vo.getVerifyCode());
                vo.setQrcode(QrCodeUtils.generatePngAsBase64Uri(type));
            }
        }
        return data;
    }

    @Override
    public boolean isNewShopCustomer(Long shopId, Long userId) {
        return tradeOrderMapper.selectCountByShopIdAndUserId(shopId, userId) == 0;
    }

    @Override
    public int getTodayOrderCount(Long shopId, Integer type) {
        return tradeOrderMapper.selectTodayOrderCount(shopId, type);
    }

    /**
     * 获得自身的代理对象，解决 AOP 生效问题
     *
     * @return 自己
     */
    private TradeOrderQueryServiceImpl getSelf() {
        return SpringUtil.getBean(getClass());
    }

}
